package com.yitiao.service;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import com.yitiao.entity.CommerceGoods;
import com.yitiao.mapper.CommerceGoodsMapper;
import org.springframework.transaction.annotation.Transactional;

@Service
//@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
@RequiredArgsConstructor
@Slf4j
public class CommerceGoodsService {

    private final CommerceGoodsMapper goodsMapper;

    private CommerceGoods goods = new CommerceGoods();

    private ReentrantLock lock = new ReentrantLock();

    private final RedisTemplate<Object, Object> redisTemplate;

    public static final String LOCK_TEST = "lock-test";

    public void batchInsert(List<CommerceGoods> list) {
        goodsMapper.batchInsert(list);
    }

    //    @Transactional(rollbackFor = Exception.class)
    public void addGoodsTran(List<CommerceGoods> goods) {
//        goodsMapper.insert(goods.get(0));
//        goods.forEach(good -> {
//            serviceCopy.insert(goods.get(1));
//        });
        CommerceGoodsService goodsService = (CommerceGoodsService) (AopContext.currentProxy());
        goodsService.insert(goods.get(0));
    }

    @Transactional(rollbackFor = Exception.class)
    void insert(CommerceGoods good) {
        goodsMapper.insert(good);
        int a = 1 / 0;
    }

    public void deStock() {
//        jvmLock();
//        mysqlLock();
//        redisAtomic();
//        casLock();
//        updateLock();
//        redisWatchLock();
        distributeLockWithRedis();
    }

    private void redisWatchLock() {
        redisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public  Object execute(RedisOperations redisOperations) throws DataAccessException {
                redisOperations.watch(LOCK_TEST);

                Long count = (Long) redisTemplate.opsForValue().get(LOCK_TEST);
                if (count!=null && count >0){
                    count = count-1;
                    redisOperations.multi();
                    redisOperations.opsForValue().set(LOCK_TEST,count);
                    List exec = redisOperations.exec();
                    if (CollectionUtil.isEmpty(exec)){
                        redisWatchLock();
                    }
                }
                return null;
            }
        });

    }

    @SneakyThrows
    private void distributeLockWithRedis() {
        String uid = UUID.randomUUID().toString();
        Boolean absent = redisTemplate.opsForValue().setIfAbsent("lock", uid,5000, TimeUnit.MILLISECONDS);
        try {
            if (absent!=null&&!absent){
                Thread.sleep(1000);
                distributeLockWithRedis();  // 栈溢出问题
            }else {
                Integer count = (Integer) redisTemplate.opsForValue().get(LOCK_TEST);
                if (count!=null && count >0){
                    count = count-1;
                    redisTemplate.opsForValue().set(LOCK_TEST,count);
                }
            }
        }finally {
            // 防误删
            Object lock = redisTemplate.opsForValue().get("lock");
            if (lock!=null&&lock.toString().equals(uid)){
                redisTemplate.delete("lock");
            }
        }
    }

    private void updateLock() {
        goodsMapper.deStockByUpdate(LOCK_TEST, 1);
    }

    private void casLock() {
        int result = 0;
        while (result == 0) {
            CommerceGoods good = goodsMapper.selectOne(Wrappers.lambdaQuery(CommerceGoods.class)
                    .eq(CommerceGoods::getGoodsName, LOCK_TEST)
                    .select());
            Long inventory = good.getInventory();
            good.setInventory(good.getInventory() - 1);
            result = goodsMapper.update(good, Wrappers.lambdaUpdate(CommerceGoods.class)
                    .eq(CommerceGoods::getInventory, inventory));
        }
    }

    private void redisAtomic() {
        redisTemplate.opsForValue().decrement(LOCK_TEST);
        Long count = (Long) redisTemplate.opsForValue().get(LOCK_TEST);
        assert count != null;
        log.info(count.toString());
    }

    //    @Transactional
    public synchronized void mysqlLock() {
        CommerceGoods good = goodsMapper.selectOne(Wrappers.lambdaQuery(CommerceGoods.class)
                .eq(CommerceGoods::getGoodsName, LOCK_TEST)
                .select()
        );
        good.setInventory(good.getInventory() - 1);
        goodsMapper.updateById(good);
        log.info(good.getInventory().toString());
    }

    private void jvmLock() {
        lock.lock();
        try {
            goods.setInventory(goods.getInventory() - 1);
            log.info(goods.getInventory().toString());
        } finally {
            lock.unlock();
        }
    }
}
